﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Projektarbete
{
    class Map
    {
        public Tile[][] map;
        public float[][] lightMap;
        public bool[][] visitedMap;
        private Texture2D tileset;

        public Point mapDimensions;

        public Point topLeft;
        public Point bottomRight; 

        private Point cullingTopLeft;
        private Point cullingBottomRight;

        public LinkedList<Merchant> merchantList;

        int visualRange = 10;

        /// <summary>
        /// Creates the map system with the following starting map
        /// </summary>
        /// <param name="map">Starting map to use</param>
        /// <param name="tileset">Tileset to use</param>
        public Map(Tile[][] map, Texture2D tileset)
        {
            this.map = map;
            this.tileset = tileset;

            topLeft = Point.Zero;
            bottomRight = Point.Zero;
            cullingTopLeft = Point.Zero;
            cullingBottomRight = Point.Zero;

            mapDimensions = new Point(map[0].Length, map.Length);

            lightMap = new float[mapDimensions.Y][];
            visitedMap = new bool[mapDimensions.Y][];

            for (int y = 0; y < mapDimensions.Y; y++)
            {
                lightMap[y] = new float[mapDimensions.X];
                visitedMap[y] = new bool[mapDimensions.X];

                for (int x = 0; x < mapDimensions.X; x++)
                {
                    lightMap[y][x] = 0.0f;
                    visitedMap[y][x] = false;
                }
            }
        }

        /// <summary>
        /// Changes the tile of a given position
        /// </summary>
        /// <param name="tile">New tile which should be put at the position</param>
        /// <param name="position">Position to put it at</param>
        public void ChangeTile(Tile tile, Point position)
        {
            map[position.Y][position.X] = tile;
        }

        /// <summary>
        /// Changes the tile of a given position
        /// </summary>
        /// <param name="tile">tile to change</param>
        /// <param name="newTile">tile to change the old tile into</param>
        public void ChangeTile(Tile tile, Tile newTile)
        {
            tile = newTile;
        }

        public Merchant GetMerchant(Point position)
        {
            Vector2 VectorPosition = new Vector2(position.Y * 24, position.X * 24);

            foreach (Merchant merchant in merchantList)
            {
                if (merchant.Position == VectorPosition)
                {
                    return merchant;
                }
            }

            throw new Exception("Borde inte hända. Ropa på David");
        }

        public void SetMerchantlist(LinkedList<Merchant> merchantList)
        {
            this.merchantList = merchantList;
        }

        public void ChangeMap(Tile[][] map)
        {
            this.map = map;
            mapDimensions = new Point(map[0].Length, map.Length);
        }

        /// <summary>
        /// Updates the lightmap
        /// </summary>
        /// <param name="playerPosition">Position of the player</param>
        public void UpdateLightmap(Point playerPosition)
        {
            for (int y = 0; y < mapDimensions.Y; y++)
            {
                for (int x = 0; x < mapDimensions.X; x++)
                {
                    lightMap[y][x] = 0.0f;
                }
            }

            lightMap[playerPosition.Y][playerPosition.X] = 1.0f;

            for (int octant = 1; octant < 9; octant++)
            {
                RecursiveScan(playerPosition, 1, octant, 1.0, 0.0);
            }

            //Fråga David om du vill veta
            cullingTopLeft.X = (int)MathHelper.Clamp(playerPosition.X - 17, 0, mapDimensions.X);
            cullingTopLeft.Y = (int)MathHelper.Clamp(playerPosition.Y - 13, 0, mapDimensions.Y);

            cullingBottomRight.X = (int)MathHelper.Clamp(playerPosition.X + 18, 0, mapDimensions.X);
            cullingBottomRight.Y = (int)MathHelper.Clamp(playerPosition.Y + 13, 0, mapDimensions.Y);

            for (int y = cullingTopLeft.Y; y < cullingBottomRight.Y; y++)
            {
                for (int x = cullingTopLeft.X; x < cullingBottomRight.X; x++)
                {
                    if (lightMap[y][x] != 1.0)
                        if (visitedMap[y][x])
                            lightMap[y][x] = 0.4f;
                }
            }
        }

        public void RecursiveScan(Point playerPosition, int depth, int octant, double startSlope, double endSlope)
        {
            int x = 0;
            int y = 0;

            switch (octant)
            {
                case 1:
                    y = playerPosition.Y - depth;
                    x = (int)Math.Round(playerPosition.X - startSlope * depth);

                    if (x < 0)
                        break;
                    if (x >= mapDimensions.X) 
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y) 
                        break;

                    while (GetSlope(x, y, playerPosition.X, playerPosition.Y) >= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                        {
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x - 1, y, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetSlope(x - 0.5, y + 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x - 1, y, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = GetSlope(x - 0.5, y - 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        }
                        x++;
                    }
                    x--;

                    break;
                case 2:
                    y = playerPosition.Y - depth;
                    x = (int)Math.Round(playerPosition.X + startSlope * depth);

                    if (x < 0) 
                        break;
                    if (x >= mapDimensions.X)
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y) 
                        break;

                    while (GetSlope(x, y, playerPosition.X, playerPosition.Y) <= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x + 1, y, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetSlope(x + 0.5, y + 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x + 1, y, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = -GetSlope(x + 0.5, y - 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        x--;
                    }
                    x++;

                    break;
                case 3:
                    x = playerPosition.X + depth;
                    y = (int)Math.Round(playerPosition.Y - startSlope * depth);

                    if (x < 0) 
                        break;
                    if (x >= mapDimensions.X) 
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y) 
                        break;

                    while (GetInvertedSlope(x, y, playerPosition.X, playerPosition.Y) <= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x, y - 1, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetInvertedSlope(x - 0.5, y - 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x, y - 1, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = -GetInvertedSlope(x + 0.5, y - 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        y++;
                    }
                    y--;

                    break;
                case 4:
                    x = playerPosition.X + depth;
                    y = (int)Math.Round(playerPosition.Y + startSlope * depth);

                    if (x < 0) 
                        break;
                    if (x >= mapDimensions.X) 
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y) 
                        break;

                    while (GetInvertedSlope(x, y, playerPosition.X, playerPosition.Y) >= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x, y + 1, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetInvertedSlope(x - 0.5, y + 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x, y + 1, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = GetInvertedSlope(x + 0.5, y + 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        y--;
                    }
                    y++;

                    break;
                case 5:
                    y = playerPosition.Y + depth;
                    x = (int)Math.Round(playerPosition.X + startSlope * depth);

                    if (x < 0) 
                        break;
                    if (x >= mapDimensions.X) 
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y)
                        break;

                    while (GetSlope(x, y, playerPosition.X, playerPosition.Y) >= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x + 1, y, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetSlope(x + 0.5, y - 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x + 1, y, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = GetSlope(x + 0.5, y + 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        x--;
                    }
                    x++;

                    break;
                case 6:
                    y = playerPosition.Y + depth;
                    x = (int)Math.Round(playerPosition.X - startSlope * depth);

                    if (x < 0) 
                        break;
                    if (x >= mapDimensions.X) 
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y) 
                        break;

                    while (GetSlope(x, y, playerPosition.X, playerPosition.Y) <= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x - 1, y, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetSlope(x - 0.5, y - 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x - 1, y, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = -GetSlope(x - 0.5, y + 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        x++;
                    }
                    x--;

                    break;
                case 7:
                    x = playerPosition.X - depth;
                    y = (int)Math.Round(playerPosition.Y + startSlope * depth);

                    if (x < 0) 
                        break;
                    if (x >= mapDimensions.X) 
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y) 
                        break;

                    while (GetInvertedSlope(x, y, playerPosition.X, playerPosition.Y) <= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x, y + 1, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetInvertedSlope(x + 0.5, y + 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x, y + 1, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = -GetInvertedSlope(x - 0.5, y + 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        y--;
                    }
                    y++;

                    break;
                case 8:
                    x = playerPosition.X - depth;
                    y = (int)Math.Round(playerPosition.Y - startSlope * depth);

                    if (x < 0) 
                        break;
                    if (x >= mapDimensions.X) 
                        break;
                    if (y < 0) 
                        break;
                    if (y >= mapDimensions.Y) 
                        break;

                    while (GetInvertedSlope(x, y, playerPosition.X, playerPosition.Y) >= endSlope)
                    {
                        if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y))
                            if (map[y][x].BlocksVision)
                            {
                                if (TestCell(x, y - 1, playerPosition.X, playerPosition.Y, false, depth))
                                    RecursiveScan(playerPosition, depth + 1, octant, startSlope, GetInvertedSlope(x + 0.5, y - 0.5, playerPosition.X, playerPosition.Y));

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                            else
                            {
                                if (TestCell(x, y - 1, playerPosition.X, playerPosition.Y, true, depth))
                                    startSlope = GetInvertedSlope(x - 0.5, y - 0.5, playerPosition.X, playerPosition.Y);

                                lightMap[y][x] = 1.0f;
                                visitedMap[y][x] = true;
                            }
                        y++;
                    }
                    y--;

                    break;
            }

            if (x < 0) 
                x = 0;
            if (x >= mapDimensions.X) 
                x = mapDimensions.X - 1;
            if (y < 0) 
                y = 0;
            if (y >= mapDimensions.Y) 
                y = mapDimensions.Y - 1;

            if (IsWithinVisualRange(playerPosition.X, playerPosition.Y, x, y) && !map[y][x].BlocksVision)
                RecursiveScan(playerPosition, depth + 1, octant, startSlope, endSlope);
        }

        private bool TestCell(int x, int y, int playerX, int playerY, bool cellState, int depth)
        {
            if (x < 0) 
                return false;
            if (x >= mapDimensions.X) 
                return false;
            if (y < 0) 
                return false;
            if (y >= mapDimensions.Y) 
                return false;

            if (IsWithinVisualRange(playerX, playerY, x, y))
                return map[y][x].BlocksVision == cellState;
            else
                return false;
        }

        private bool IsWithinVisualRange(int point1X, int point1Y, int point2X, int point2Y)
        {
            if (point1X == point2X)
                return Math.Abs(point1Y - point2Y) <= visualRange;

            if (point1Y == point2Y)
                return Math.Abs(point1X - point2X) <= visualRange;

            return GetDistanceSquared(point1X, point1Y, point2X, point2Y) <= Math.Pow(visualRange, 2);
        }

        private double GetSlope(double point1X, double point1Y, double point2X, double point2Y)
        {
            return (point1X - point2X) / (point1Y - point2Y);
        }

        private double GetInvertedSlope(double point1X, double point1Y, double point2X, double point2Y)
        {
            return (point1Y - point2Y) / (point1X - point2X);
        }

        private int GetDistanceSquared(int point1X, int point1Y, int point2X, int point2Y)
        {
            if (point1X == point2X)
                return point1X - point2X;

            if (point1Y == point2Y)
                return point1Y - point2Y;

            return (int)(Math.Pow(point1X - point2X, 2) + Math.Pow(point1Y - point2Y, 2));
        }

        public void DrawMap(SpriteBatch spriteBatch, Vector2 tiledPosition)
        {
            for (int y = cullingTopLeft.Y; y < cullingBottomRight.Y; y++)
            {
                for (int x = cullingTopLeft.X; x < cullingBottomRight.X; x++)
                {
                    spriteBatch.Draw(tileset, new Vector2(x * 24, y * 24), new Rectangle((int)map[y][x].getDrawRow * 24, (int)map[y][x].getDrawColumn * 24, 24, 24), Color.White * lightMap[y][x]);
                }
            }
        }
    }
}